#! /usr/bin/env python
"""
TODO:

I intend (possibly) to write other segments that report (but not log):
1.  items (name and/or stats, if that's even possible)
2.  reputation (more viable, and somewhat practical)

Also I want to clean up the code to make it more modular.

Also I want to (but don't really anticipate that I will) set it up so if a character wasn't updated on the wowarmory server it won't process the character's data again.
"""

import urllib2
import xml.dom.minidom
import sys
import logging
import os
import time

logpath = os.path.normpath('/home/derek/.wowarm') + '/'     # needs to end in /
armoryserver = 'http://www.wowarmory.com/'                  # needs to end in /

checkmelee = checkranged = checkspell = checktank = checkarena = checkresil = checkrogue = True    # it's possible to change these at the command line but I don't feel like implementing that atm
#checkmelee = False     # in case you want to disable data tracking -- shouldn't need to disable these normally
#checkranged = False
#checkspell = False
#checktank = False
#checkarena = False
#checkresil = False

class wowarm:

    def __init__(self):
        commandLine = sys.argv.pop(0)   # get that out of the way, and it is practical
        try: realm, char = sys.argv.pop(0), sys.argv.pop(0)   # command line arguments
        except IndexError:
            print 'The first argument must be the realm name, the second must be the character name. (Use + instead of a space on the realm name.)'
            print commandLine, '<realm name> <character name>'
            sys.exit()

        for arg in sys.argv: pass # this might be used eventually to process more args

        charsheet = 'character-sheet.xml'
        skillsheet = 'character-skills.xml'
        source = self.fetch(charsheet, realm, char)

        try: charcore = source.getElementsByTagName('character')[0]
        except IndexError: print '%s does not exist on %s or the wowarmory is inaccessible.' % (init_char, init_realm); sys.exit()
        chartab = source.getElementsByTagName('characterTab')[0]
        char = charcore.getAttribute('name')
        realm = charcore.getAttribute('realm')
        charclassid = charcore.getAttribute('classId')
        self.charfile = logpath + char

        logstate, lastmodtime = self.initializeLog(charcore, self.charfile, char, realm)
        self.characterCore(logstate, charcore, char)
        self.characterTab(chartab, char)
        self.get_stats(charclassid, chartab)

        try: arena = checkarena and charcore.getElementsByTagName('arenaTeams')[0].getElementsByTagName('arenaTeam')
        except IndexError: arena = False
        if arena:
            self.get_arena(arena)

        if checkrogue and charclassid == '4':      # if the character is a rogue
            skillsource = self.fetch(skillsheet, realm, char, False)
            skillcats = skillsource.getElementsByTagName('skillTab')[0].getElementsByTagName('skillCategory')
            for cat in skillcats:
                if cat.getAttribute('key') == 'classskills':
                    classskills = cat.getElementsByTagName('skill')
                    break
            for skill in classskills:
                if skill.getAttribute('key') in ['lockpicking', 'poisons']:
                    logging.info('%20s  %s', skill.getAttribute('name'), skill.getAttribute('value'))

        logging.shutdown()

        if logstate:
            accesstime = os.stat(self.charfile).st_atime
            os.utime(self.charfile, (accesstime, lastmodtime))
            print 'Modification time on logfile %s set to last Armory update time.' % self.charfile

    def fetch(self, sheet, realm, character, visual = True):
        if visual: print 'Making request to server.',
        get_url = "%s/%s?r=%s&n=%s" % (armoryserver, sheet, realm, character)
        oOpener = urllib2.build_opener()
        oOpener.addheaders = [('user-agent', 'Firefox/2.0 - wowarmpy 0.3'),]
        req = urllib2.Request(get_url)
        strFile = oOpener.open(req).read()
        if visual: print 'Received file.'
        oDoc = xml.dom.minidom.parseString(strFile)
        return oDoc

    def initializeLog(self, core, logfile, charname, charrealm):
        lastmodtime = time.mktime(time.strptime(core.getAttribute('lastModified'), '%B %d, %Y'))

        try:    #see if the log file is already there
            openlog = open(logfile, 'r')
            openlog.close()
            logstate = 'append'
            logdate = os.stat(logfile).st_mtime
            diff = 'log is %d days old' % ((lastmodtime - logdate)/86400)
            lastlog = time.strftime('%B %d, %Y at %H:%M', time.localtime(logdate))
            if lastmodtime >= logdate: logstate = False; print 'No update from server. Canceling log, but passing information.'

        except IOError: #file doesn't exist yet; see if we can create it:
            try:
                openlog = open(self.charfile, 'w')
                openlog.close()
                logstate = 'fresh'
                print 'First time seeing %s.' % charname
            except IOError: # file can't be created because the value of self.logpath doesn't exist as a directory
                print 'You must create the following path according to the variable set in the code. Feel free to change the code as necessary. The variable name is %s.' % 'logpath'
                print logpath
                sys.exit()

        logging.basicConfig(
            level = logging.INFO,
            format = '%(levelname)-8s %(message)s',
            filename = logstate and self.charfile,
            filemode = 'a',
            )

        if logstate:
            console = logging.StreamHandler() # define a Handler which writes INFO messages or higher to the sys.stderr
            console.setLevel(logging.DEBUG)
            formatter = logging.Formatter('%(levelname)-8s %(message)s') # set a format which is simpler for console use
            console.setFormatter(formatter) # tell the handler to use this format
            logging.getLogger('').addHandler(console) # add the handler to the root logger; don't know what this does, but it's necessary to send it to the console
            print 'Logging to: %s\n' % self.charfile
        if os.name in ['posix', 'mac']:   # if the system is unix or mac, set up a hard link
            if not os.path.exists(logpath + charrealm): os.mkdir(logpath + charrealm)
            try: os.link(self.charfile, logpath + charrealm + '/' + charname)
            except: pass    # if links can't work, don't worry about it.
        return logstate, lastmodtime

    def characterCore(self, logstate, core, charname): #load up the character's basics
        # immutable attributes
        self.charclass = core.getAttribute('class')     # self. because charclass is used in the spec line
        charrace = core.getAttribute('race')
        chargender = core.getAttribute('gender')
        charfaction = core.getAttribute('faction')
        charrealm = core.getAttribute('realm')
        charbg = core.getAttribute('battleGroup')
        titledname = core.getAttribute('prefix') + charname + core.getAttribute('suffix')

        logform = [
            '%s',               # self.titledname
            '%s (%s) - %s',     # charrealm, charbg, charfaction
            '%s - %s - %s\n',   # self.charclass, charrace, chargender
            ]

        if logstate == 'fresh':
            logging.info(logform[0], titledname)
            logging.info(logform[1], charrealm, charbg, charfaction)
            logging.info(logform[2], self.charclass, charrace, chargender)
        else:
            print
            print logform[0] % (titledname)
            print logform[1] % (charrealm, charbg, charfaction)
            print logform[2] % (self.charclass, charrace, chargender)
            #print

        # mutable attributes
        lastmod = core.getAttribute('lastModified')
        self.charlevel = int(core.getAttribute('level'))    # self. because charlevel is used in the armor mitigation line
        guildname = core.getAttribute('guildName') and '<' + core.getAttribute('guildName') + '>'
        logging.info('='*35)
        logging.info('Updated: %s', lastmod)
        logging.info('%s - Level %d\n', guildname or '/guildless/', self.charlevel)

    def characterTab(self, chartab, charname):
        knowntitles = chartab.getElementsByTagName('knownTitles')[0].getElementsByTagName('title')
        titles = ''
        for title in knowntitles:
            titles = title.getAttribute('value') % charname + (titles and '; ' + titles)
        logging.info('Known titles: %s', titles or 'none')

        talents = chartab.getElementsByTagName('talentSpec')[0]
        spec = '%d / %d / %d' % (int(talents.getAttribute('treeOne')), int(talents.getAttribute('treeTwo')), int(talents.getAttribute('treeThree')))
        logging.info('%20s  %s', self.charclass, spec)

        kills = int(chartab.getElementsByTagName('pvp')[0].getElementsByTagName('lifetimehonorablekills')[0].getAttribute('value'))
        logging.info('%20s  %d', 'honor kills', kills)

        professions = chartab.getElementsByTagName('professions')[0].getElementsByTagName('skill')
        for prof in professions:
            logging.info('%20s  %s', prof.getAttribute('name'), prof.getAttribute('value'))

        buffs = chartab.getElementsByTagName('buffs')[0].getElementsByTagName('spell')
        if len(buffs) == 0: logging.info('No buffs.')
        for buff in buffs:
            logging.info('BUFF: %s -- %s', buff.getAttribute('name'), buff.getAttribute('effect'))

        debuffs = chartab.getElementsByTagName('debuffs')[0].getElementsByTagName('spell')
        if len(debuffs) == 0: logging.info('No debuffs.')
        for debuff in debuffs:
            logging.info('DEBUFF: %s -- %s', debuff.getAttribute('name'), buff.getAttribute('effect'))

        bars = chartab.getElementsByTagName('characterBars')[0]
        health = int(bars.getElementsByTagName('health')[0].getAttribute('effective'))
        logging.info('%20s  %5d', 'health', health)

        secondBar = chartab.getElementsByTagName('secondBar')[0]
        if secondBar.getAttribute('type') == 'm':
            mana = int(secondBar.getAttribute('effective'))
            mp5 = int(secondBar.getAttribute('casting'))
            logging.info('%20s  %5d  + %d mp5', 'mana', mana, mp5)
        if secondBar.getAttribute('type') == 'e':
            mana = int(secondBar.getAttribute('effective'))
            if not mana == 100: logging.info('%20s  %d', 'energy', mana)

    def get_stats(self, charclass, chartab):
        # classId = 6, 10 don't seem to be used

        if charclass == (6 or 10): logging.warning('==========CLASSID %d', charclassid)

        melee = checkmelee and chartab.getElementsByTagName('melee')[0]
        ranged = checkranged and chartab.getElementsByTagName('ranged')[0]
        spell = chartab.getElementsByTagName('spell')
        caster = checkspell and spell[len(spell) - 1]
        defenses = chartab.getElementsByTagName('defenses')[0]
        tank = checktank and defenses
        resil = defenses.getElementsByTagName('resilience')[0]

        meleeclasses = [
            '1',  #warrior
            '2',  #paladin
            '4',  #rogue
            '7',  #shaman
            #'11', #druid -- removed because they should never use a melee weapon for their dps
            ]

        rangedclasses = [
            '3',  #hunter
            ]

        casterclasses = [
            '2', ['holy'],                       #paladin
            '5', ['holy', 'shadow'],             #priest
            '7', ['fire', 'frost', 'nature'],    #shaman
            '8', ['arcane', 'fire', 'frost'],    #mage
            '9', ['fire', 'shadow'],             #warlock
            '11', ['arcane', 'nature'],          #druid
            ]

        healerclasses = [
            '2',  #paladin
            '5',  #priest
            '7',  #shaman
            '11', #druid
            ]

        tankclasses = [
            '1',  #warrior
            '2',  #paladin
            '4',  #rogue -- mostly for the whole dodge/parry thing
            '11', #druid
            ]

        meleeclass = rangedclass = casterclass = tankclass = ''

        if melee and charclass in meleeclasses:
            mainhand = melee.getElementsByTagName('mainHandDamage')[0]
            offhand = melee.getElementsByTagName('offHandDamage')[0]
            MHdps = float(mainhand.getAttribute('dps'))
            OHdps = float(offhand.getAttribute('dps'))
            MHspeed = float(mainhand.getAttribute('speed'))
            OHspeed = float(offhand.getAttribute('speed'))
            crit = float(melee.getElementsByTagName('critChance')[0].getAttribute('percent'))
            logform = '%20s  %7.1f, speed %4.2f'
            logging.info(logform, 'Main hand', MHdps, MHspeed)
            logging.info(logform, 'Off hand',  OHdps, OHspeed)
            logging.info('%20s  %8.2f %%\n', 'critChance', crit)

        if ranged and charclass in rangedclasses:
            damage = ranged.getElementsByTagName('damage')[0]
            dps = float(damage.getAttribute('dps'))
            speed = float(damage.getAttribute('speed'))
            crit = float(ranged.getElementsByTagName('critChance')[0].getAttribute('percent'))
            logging.info('%20s  %7.1f, %5.2f, %5.2f %%\n', 'ranged', dps, speed, crit)

        if caster and charclass in casterclasses:
            i = casterclasses.index(charclass) + 1
            bonusDamage = caster.getElementsByTagName('bonusDamage')[0]
            critChance = caster.getElementsByTagName('critChance')[0]
            for school in casterclasses[i]:
                bonus = int(bonusDamage.getElementsByTagName(school)[0].getAttribute('value'))
                crit = float(critChance.getElementsByTagName(school)[0].getAttribute('percent'))
                logging.info('%20s  %5d, %5.2f %%', school, bonus, crit)

        if caster and charclass in healerclasses:
            bonusHealing = int(caster.getElementsByTagName('bonusHealing')[0].getAttribute('value'))
            logging.info('%20s  %5d\n', 'bonusHealing', bonusHealing)

        if tank and charclass in tankclasses:
            armor = tank.getElementsByTagName('armor')[0]
            basearmor = int(armor.getAttribute('base'))
            mitig = float(armor.getAttribute('percent'))
            dodge = float(tank.getElementsByTagName('dodge')[0].getAttribute('percent'))
            parry = float(tank.getElementsByTagName('parry')[0].getAttribute('percent'))
            block = float(tank.getElementsByTagName('block')[0].getAttribute('percent'))
            logform = '%20s  %5.2f %%'
            logging.info('%20s  %d (%5.2f %% against lvl %d)', 'armor', basearmor, mitig, self.charlevel)
            logging.info(logform, 'dodge', dodge)
            logging.info(logform, 'parry', parry)
            logging.info(logform + '\n', 'block', block)

        if checkresil and resil:
            damage = float(resil.getAttribute('damagePercent'))
            hit = float(resil.getAttribute('hitPercent'))
            value = '%d' % float(resil.getAttribute('value'))
            logging.info('%20s  -%.2f %% dmg / -%.2f %% hit', value + ' resilience', damage, hit)

    def get_arena(self, arena):
        for team in arena:
            name = team.getAttribute('name')
            rating = team.getAttribute('rating')
            games = team.getAttribute('seasonGamesPlayed')
            size = team.getAttribute('size')
            logging.info('%-50s %4s / %s games', '(' + size + ') ' + name, rating, games)

if __name__ == "__main__":
    wowarm()
    if not os.name in ['posix', 'mac']: raw_input('Press enter when finished. If you run this script from the command line, remove the last line of this code.')
